"""
    B站视频下载器 - 优化版
    支持断点续传、后台合并、状态跟踪、分辨率选择和Cookie登录
    cookie信息放入单独文件中，不需要每次打开都手动输入
    修复：找不到匹配的分辨率问题
    v3.3 新增：Cookie有效性验证、分辨率信息打印
"""

# 导入模块
import sys
import re
import os
import json
import subprocess
import requests
import lxml.etree
import logging
import webbrowser
import time
from datetime import datetime
from urllib.parse import urlsplit, urlunsplit, parse_qs, urlencode
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                            QLabel, QLineEdit, QPushButton, QFileDialog,
                            QProgressBar, QMessageBox, QListWidget, QListWidgetItem,
                            QCheckBox, QSpacerItem, QSizePolicy, QGridLayout, QComboBox,
                            QSplitter, QFrame)
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QSize
from PyQt5.QtGui import QIcon, QFont, QColor, QPalette

# 设置日志
def setup_logger(save_dir):
    log_dir = os.path.join(save_dir, "logs")
    os.makedirs(log_dir, exist_ok=True)
    
    log_file = os.path.join(log_dir, f"download_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
    
    logger = logging.getLogger("BilibiliDownloader")
    logger.setLevel(logging.INFO)
    
    # 文件处理器
    file_handler = logging.FileHandler(log_file)
    file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
    
    # 控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
    
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger, log_file

class DownloadThread(QThread):
    """下载线程"""
    update_status = pyqtSignal(str, str, str)  # cid, status, message
    update_progress = pyqtSignal(int, int)  # current, total
    finished = pyqtSignal()
    error = pyqtSignal(str)
    resolution_warning = pyqtSignal(str, str, int, int)  # cid, title, requested, available
    cookie_status = pyqtSignal(bool, str)  # is_valid, message
    
    def __init__(self, url, save_dir, resume=False, resolution_id=0, cookie=""):
        super().__init__()
        self.url = url
        self.save_dir = save_dir
        self.resume = resume
        self.resolution_id = resolution_id  # 用户选择的分辨率ID
        self.cookie = cookie  # 用户提供的Cookie
        self.canceled = False
        self.logger, self.log_file = setup_logger(save_dir)
        self.status_file = os.path.join(save_dir, "download_status.json")
        self.download_status = {}
        self.headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
            'referer': 'https://www.bilibili.com/'
        }
        
        # 如果有cookie，添加到请求头
        if self.cookie:
            self.headers['cookie'] = self.cookie
        
        # 检查ffmpeg
        self.ffmpeg_available = self.check_ffmpeg()
        if not self.ffmpeg_available:
            self.logger.error("FFmpeg未安装，请安装FFmpeg后再使用本程序")
            self.error.emit("FFmpeg未安装，请安装FFmpeg后再使用本程序")
        
        # 分辨率名称映射
        self.resolution_names = {
            0: "自动选择最高画质",
            16: "360P 流畅",
            32: "480P 标清",
            64: "720P 准高清",
            80: "1080P 高清",
            112: "1080P 高码率",
            116: "1080P 60帧",
            120: "4K 超高清"
        }

    def run(self):
        try:
            if not self.ffmpeg_available:
                return
            
            # 验证Cookie有效性
            if self.cookie:
                self.logger.info("正在验证Cookie有效性...")
                self.update_status.emit("", "info", "正在验证Cookie有效性...")
                cookie_valid, message = self.verify_cookie()
                self.cookie_status.emit(cookie_valid, message)
                
                if not cookie_valid:
                    self.logger.error(f"Cookie验证失败: {message}")
                    self.error.emit(f"Cookie验证失败: {message}")
                    return
                
                self.logger.info(f"Cookie验证成功: {message}")
                self.update_status.emit("", "info", f"Cookie验证成功: {message}")
            
            self.load_download_status()
            self.download_videos()
        except Exception as e:
            self.logger.exception(f"发生错误: {str(e)}")
            self.error.emit(f"发生错误: {str(e)}")
        finally:
            self.finished.emit()

    def verify_cookie(self):
        """验证Cookie有效性"""
        url = "https://api.bilibili.com/x/web-interface/nav"
        try:
            response = requests.get(url, headers=self.headers, timeout=10)
            data = response.json()
            
            if data.get('code') == 0:
                if data.get('data', {}).get('isLogin', False):
                    username = data.get('data', {}).get('uname', '未知用户')
                    return True, f"已登录: {username}"
                return False, "账号未登录"
            else:
                message = data.get('message', '未知错误')
                return False, f"验证失败: {message}"
        except Exception as e:
            return False, f"请求异常: {str(e)}"

    def check_ffmpeg(self):
        """检查ffmpeg是否可用"""
        try:
            subprocess.run(["ffmpeg", "-version"], 
                           stdout=subprocess.DEVNULL, 
                           stderr=subprocess.DEVNULL,
                           creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0)
            return True
        except (FileNotFoundError, subprocess.CalledProcessError):
            return False

    def load_download_status(self):
        """加载下载状态"""
        if os.path.exists(self.status_file) and self.resume:
            try:
                with open(self.status_file, 'r', encoding='utf-8') as f:
                    self.download_status = json.load(f)
                self.logger.info(f"加载下载状态: 找到 {len(self.download_status)} 个任务记录")
            except Exception as e:
                self.logger.error(f"加载下载状态失败: {str(e)}")
                self.download_status = {}

    def save_download_status(self):
        """保存下载状态"""
        try:
            with open(self.status_file, 'w', encoding='utf-8') as f:
                json.dump(self.download_status, f, ensure_ascii=False, indent=2)
        except Exception as e:
            self.logger.error(f"保存下载状态失败: {str(e)}")

    def download_videos(self):
        """下载视频主逻辑"""
        # 获取基础页面数据
        self.logger.info("正在获取视频信息...")
        self.update_status.emit("", "info", "正在获取视频信息...")
        
        try:
            base_resp = requests.get(url=self.url, headers=self.headers, timeout=30)
            base_resp.raise_for_status()
        except Exception as e:
            self.logger.error(f"获取视频信息失败: {str(e)}")
            self.error.emit(f"获取视频信息失败: {str(e)}")
            return

        # 提取视频数据
        pattern = r'"videoData":(.*?),"availableVideoList"'
        match = re.search(pattern, base_resp.text)
        if not match:
            self.logger.error("未找到视频数据，请检查URL是否正确")
            self.error.emit("未找到视频数据，请检查URL是否正确")
            return
            
        try:
            video_data = json.loads(match.group(1))
        except json.JSONDecodeError as e:
            self.logger.error(f"解析视频数据失败: {str(e)}")
            self.error.emit(f"解析视频数据失败: {str(e)}")
            return
            
        ugc_season = video_data.get('ugc_season')
        html = lxml.etree.HTML(base_resp.text)

        # 构建下载任务列表
        tasks = []
        if ugc_season:  # 合集视频
            self.logger.info("检测到合集视频...")
            self.update_status.emit("", "info", "检测到合集视频...")
            
            playlist = html.xpath('//div[@class="slide-item active"]/@title')
            target_section = next((s for s in ugc_season['sections'] 
                                 if playlist and s['title'] == playlist[0]), ugc_season['sections'][0])
            
            for episode in target_section['episodes']:
                bvid = episode['bvid']
                for page in episode['pages']:
                    cid = page['cid']
                    # 如果启用了断点续传且任务已完成，则跳过
                    if self.resume and self.download_status.get(str(cid), {}).get('status') == 'completed':
                        self.logger.info(f"跳过已完成的任务: CID={cid}, 标题={page['part']}")
                        self.update_status.emit(str(cid), "skip", f"跳过已完成: {page['part']}")
                        continue
                    tasks.append((bvid, page))
                
        else:  # 普通多P视频
            self.logger.info("检测到多P视频...")
            self.update_status.emit("", "info", "检测到多P视频...")
            
            bvid = video_data['bvid']
            for page in video_data['pages']:
                cid = page['cid']
                # 如果启用了断点续传且任务已完成，则跳过
                if self.resume and self.download_status.get(str(cid), {}).get('status') == 'completed':
                    self.logger.info(f"跳过已完成的任务: CID={cid}, 标题={page['part']}")
                    self.update_status.emit(str(cid), "skip", f"跳过已完成: {page['part']}")
                    continue
                tasks.append((bvid, page))

        total = len(tasks)
        self.logger.info(f"找到 {total} 个视频任务")
        self.update_status.emit("", "info", f"找到 {total} 个视频任务")
        self.update_progress.emit(0, total)

        # 执行下载任务
        for idx, (bvid, page) in enumerate(tasks, 1):
            if self.canceled:
                self.logger.info("下载已取消")
                self.update_status.emit("", "cancel", "下载已取消")
                return
                
            cid = page['cid']
            title = page['part']
            page_num = page['page']
            
            # 初始化任务状态
            task_status = self.download_status.setdefault(str(cid), {
                'cid': cid,
                'title': title,
                'page': page_num,
                'bvid': bvid,
                'status': 'pending',
                'video_downloaded': False,
                'audio_downloaded': False,
                'merged': False,
                'output_file': '',
                'start_time': datetime.now().isoformat()
            })
            
            # 显示选择的分辨率
            selected_res_name = self.resolution_names.get(self.resolution_id, f"未知({self.resolution_id})")
            self.logger.info(f"开始处理视频 {idx}/{total}: CID={cid}, 标题={title}, 选择分辨率: {selected_res_name}")
            self.update_status.emit(str(cid), "start", f"开始下载: {title} (选择分辨率: {selected_res_name})")
            self.update_progress.emit(idx, total)

            # 更新任务状态
            task_status['status'] = 'downloading'
            self.save_download_status()
            
            # 获取实际播放页
            video_url = self.generate_new_url(self.url, bvid, page_num)
            
            try:
                self.logger.info(f"请求播放页: {video_url}")
                resp = requests.get(video_url, headers=self.headers, timeout=30)
                resp.raise_for_status()
            except Exception as e:
                self.logger.error(f"获取播放页失败: {str(e)}")
                self.update_status.emit(str(cid), "error", f"获取播放页失败: {str(e)}")
                task_status['status'] = 'error'
                task_status['error'] = str(e)
                self.save_download_status()
                continue
            
            # 获取媒体URL
            video_url, audio_url, resolution_warning, actual_res_id = self.get_media_urls(resp.text, cid, title)
            if not video_url or not audio_url:
                error_msg = "获取媒体链接失败"
                self.logger.error(error_msg)
                self.update_status.emit(str(cid), "error", error_msg)
                task_status['status'] = 'error'
                task_status['error'] = error_msg
                self.save_download_status()
                continue
            
            # 如果有分辨率警告，发送信号
            if resolution_warning:
                self.resolution_warning.emit(str(cid), title, resolution_warning[0], resolution_warning[1])
            
            # 显示实际分辨率
            actual_res_name = self.resolution_names.get(actual_res_id, f"未知({actual_res_id})")
            self.logger.info(f"视频 {title} 实际下载分辨率: {actual_res_name}")
            self.update_status.emit(str(cid), "info", f"实际下载分辨率: {actual_res_name}")
                
            # 下载视频
            video_success = self.download_media(video_url, cid, '视频', task_status)
            if not video_success:
                continue
                
            # 下载音频
            audio_success = self.download_media(audio_url, cid, '音频', task_status)
            if not audio_success:
                continue
                
            # 合并文件
            merge_success = self.merge_files(cid, title, idx, task_status)
            
            # 更新任务状态
            if merge_success:
                task_status['status'] = 'completed'
                task_status['end_time'] = datetime.now().isoformat()
                self.save_download_status()

        self.logger.info(f"全部任务处理完成! 共处理 {total} 个视频")
        self.update_status.emit("", "completed", f"全部完成! 共处理 {total} 个视频")

    def generate_new_url(self, url_base, bvid, page):
        """生成新的分P视频URL"""
        scheme, netloc, path, query, fragment = urlsplit(url_base)
        path_segments = path.split('/')
        for i in range(len(path_segments)-1, -1, -1):
            if path_segments[i]:
                path_segments[i] = bvid
                break
        new_path = '/'.join(path_segments)
        
        params = parse_qs(query)
        params['p'] = [str(page)]
        new_query = urlencode(params, doseq=True)
        
        return urlunsplit((scheme, netloc, new_path, new_query, fragment))

    def get_media_urls(self, resp_text, cid, title):
        """从响应文本中提取高清音视频URL"""
        resolution_warning = None
        actual_res_id = self.resolution_id  # 默认为用户选择的分辨率
        pattern = r'"video":(.*?),"dolby"'
        result = re.search(pattern, resp_text)
        if not result:
            self.logger.error("未找到视频数据")
            return None, None, resolution_warning, actual_res_id
            
        try:
            json_str = '{' + f'"video":{result.group(1)}' + '}'
            data = json.loads(json_str)
        except json.JSONDecodeError as e:
            self.logger.error(f"解析视频数据失败: {str(e)}")
            return None, None, resolution_warning, actual_res_id
        
        # 记录可用的视频流ID
        available_ids = [stream['id'] for stream in data['video']]
        self.logger.info(f"可用的视频流ID: {available_ids}")
        
        # 选择指定分辨率的视频
        if self.resolution_id != 0:  # 0 表示自动选择最高画质
            # 查找指定分辨率
            selected_stream = None
            for stream in data['video']:
                if stream['id'] == self.resolution_id:
                    selected_stream = stream
                    break
            
            # 如果找到指定分辨率
            if selected_stream:
                video_url = self.get_best_url(selected_stream)
                self.logger.info(f"已选择指定分辨率: ID={self.resolution_id}")
                actual_res_id = self.resolution_id
                
                # 检查实际分辨率是否与请求匹配
                if selected_stream['id'] != self.resolution_id:
                    resolution_warning = (self.resolution_id, selected_stream['id'])
                    actual_res_id = selected_stream['id']
            else:
                # 没找到指定分辨率，尝试降级选择
                # 定义分辨率降级顺序（从高到低）
                resolution_fallback = [120, 116, 112, 80, 64, 32, 16]
                
                # 找到用户选择的分辨率在降级列表中的位置
                try:
                    start_index = resolution_fallback.index(self.resolution_id)
                except ValueError:
                    start_index = 0
                    
                # 按降级顺序查找可用的分辨率
                for rid in resolution_fallback[start_index:]:
                    for stream in data['video']:
                        if stream['id'] == rid:
                            selected_stream = stream
                            break
                    if selected_stream:
                        break
                
                if selected_stream:
                    video_url = self.get_best_url(selected_stream)
                    self.logger.warning(f"未找到ID={self.resolution_id}的分辨率，已使用ID={selected_stream['id']}替代")
                    
                    # 记录分辨率警告（请求的分辨率ID和实际使用的分辨率ID）
                    resolution_warning = (self.resolution_id, selected_stream['id'])
                    actual_res_id = selected_stream['id']
                else:
                    # 没找到任何合适的分辨率，使用最高画质
                    sorted_streams = sorted(data['video'], key=lambda x: x['id'], reverse=True)
                    if sorted_streams:
                        video_url = self.get_best_url(sorted_streams[0])
                        self.logger.warning(f"未找到ID={self.resolution_id}的分辨率，已使用最高画质替代")
                        resolution_warning = (self.resolution_id, sorted_streams[0]['id'])
                        actual_res_id = sorted_streams[0]['id']
                    else:
                        self.logger.error("没有可用的视频流")
                        return None, None, resolution_warning, actual_res_id
        else:
            # 自动选择最高画质
            sorted_streams = sorted(data['video'], key=lambda x: x['id'], reverse=True)
            if sorted_streams:
                video_url = self.get_best_url(sorted_streams[0])
                actual_res_id = sorted_streams[0]['id']
            else:
                self.logger.error("没有可用的视频流")
                return None, None, resolution_warning, actual_res_id
        
        # 选择最高质量的音频
        sorted_audio = sorted(data['audio'], key=lambda x: x['id'], reverse=True)
        if sorted_audio:
            audio_url = self.get_best_url(sorted_audio[0])
        else:
            self.logger.error("没有可用的音频流")
            return None, None, resolution_warning, actual_res_id
        
        return video_url, audio_url, resolution_warning, actual_res_id

    def get_best_url(self, stream):
        """从流中获取最佳URL"""
        # 优先使用baseUrl，其次是backupUrl
        if stream.get('baseUrl'):
            return stream['baseUrl']
        elif stream.get('base_url'):
            return stream['base_url']
        elif stream.get('backupUrl') and stream['backupUrl']:
            return stream['backupUrl'][0]
        elif stream.get('backup_url') and stream['backup_url']:
            return stream['backup_url'][0]
        return None

    def download_media(self, url, cid, media_type, task_status):
        """下载视频或音频"""
        ext = '.mp4' if media_type == '视频' else '.mp3'
        file_path = f"{cid}{ext}"
        
        # 检查是否已下载
        if os.path.exists(file_path) and self.resume:
            self.logger.info(f"检测到已下载的{media_type}文件，跳过下载")
            self.update_status.emit(str(cid), "skip", f"已下载{media_type}")
            task_status[f"{media_type.lower()}_downloaded"] = True
            self.save_download_status()
            return True
            
        self.logger.info(f"开始下载 {media_type}...")
        # 不发送下载中状态到UI，减少UI更新
        
        try:
            response = requests.get(url, headers=self.headers, timeout=30, stream=True)
            response.raise_for_status()
                
            total_size = int(response.headers.get('content-length', 0))
            downloaded = 0
            chunk_size = 1024 * 1024  # 1MB
            
            with open(file_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=chunk_size):
                    if self.canceled:
                        return False
                    if chunk:
                        f.write(chunk)
                        downloaded += len(chunk)
            
            self.logger.info(f"{media_type}下载完成: {file_path}")
            # 不发送下载完成状态到UI，减少UI更新
            task_status[f"{media_type.lower()}_downloaded"] = True
            self.save_download_status()
            return True
        except Exception as e:
            self.logger.error(f"下载{media_type}出错: {str(e)}")
            self.update_status.emit(str(cid), "error", f"下载{media_type}失败")
            task_status['status'] = 'error'
            task_status['error'] = f"下载{media_type}失败: {str(e)}"
            self.save_download_status()
            return False

    def sanitize_filename(self, filename):
        """清理文件名中的特殊字符"""
        cleaned = re.sub(r'[\\/*?:"<>|【】]', '', filename).replace(' ', '_').rstrip('.')
        return cleaned[:100]

    def merge_files(self, cid, title, idx, task_status):
        """合并音视频文件"""
        # 检查是否已合并
        output_file = task_status.get('output_file', '')
        if output_file and os.path.exists(output_file) and self.resume:
            self.logger.info(f"检测到已合并的文件，跳过合并: {output_file}")
            self.update_status.emit(str(cid), "skip", "已合并")
            task_status['merged'] = True
            self.save_download_status()
            return True
            
        clean_title = self.sanitize_filename(title)
        output_file = f"{self.save_dir}/{idx}-{clean_title}.mp4"
        task_status['output_file'] = output_file
        
        self.logger.info(f"开始合并文件: {output_file}")
        self.update_status.emit(str(cid), "merging", "合并中...")
        
        try:
            # 使用后台进程合并
            command = [
                "ffmpeg",
                "-i", f"{cid}.mp4",
                "-i", f"{cid}.mp3",
                "-c:v", "copy",
                "-c:a", "aac",
                "-b:a", "192k",
                "-y",  # 覆盖输出文件
                output_file
            ]
            
            # 在后台运行ffmpeg
            creation_flags = 0
            if sys.platform == "win32":
                creation_flags = subprocess.CREATE_NO_WINDOW
                
            process = subprocess.Popen(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                creationflags=creation_flags
            )
            
            # 等待进程完成
            stdout, stderr = process.communicate()
            if process.returncode != 0:
                self.logger.error(f"合并失败, 错误码: {process.returncode}")
                self.logger.error(f"FFmpeg输出: {stderr.decode('utf-8', errors='ignore')}")
                self.update_status.emit(str(cid), "error", "合并失败")
                task_status['status'] = 'error'
                task_status['error'] = f"合并失败, FFmpeg错误码: {process.returncode}"
                self.save_download_status()
                return False
            
            # 清理临时文件
            if os.path.exists(f"{cid}.mp4"):
                os.remove(f"{cid}.mp4")
            if os.path.exists(f"{cid}.mp3"):
                os.remove(f"{cid}.mp3")
                
            self.logger.info(f"文件合并完成: {output_file}")
            self.update_status.emit(str(cid), "completed", "合并完成")
            task_status['merged'] = True
            self.save_download_status()
            return True
        except Exception as e:
            self.logger.error(f"合并文件时出错: {str(e)}")
            self.update_status.emit(str(cid), "error", f"合并失败: {str(e)}")
            task_status['status'] = 'error'
            task_status['error'] = f"合并失败: {str(e)}"
            self.save_download_status()
            return False

    def cancel(self):
        """取消下载"""
        self.canceled = True


class BilibiliDownloader(QMainWindow):
    """主窗口"""
    def __init__(self):
        super().__init__()
        self.setWindowTitle("B站视频下载器")
        self.setGeometry(300, 300, 800, 600)
        
        # 设置应用ID，确保任务栏图标正确显示
        if sys.platform == "win32":
            import ctypes
            app_id = "Bilibili.Downloader.V1"
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
        
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        
        self.layout = QVBoxLayout()
        self.central_widget.setLayout(self.layout)
        
        self.init_ui()
        self.download_thread = None
        self.log_file = ""
        self.cookie = ""  # 存储加载的Cookie

    def init_ui(self):
        """初始化界面"""
        # 创建网格布局确保标签和输入框对齐
        grid_layout = QGridLayout()
        grid_layout.setColumnStretch(1, 1)  # 设置第二列可拉伸
        
        # URL输入 - 标签右对齐
        url_label = QLabel("视频网址:")
        url_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        grid_layout.addWidget(url_label, 0, 0)  # 第0行，第0列
        
        self.url_input = QLineEdit()
        self.url_input.setPlaceholderText("请输入Bilibili视频或合集的网址")
        self.url_input.setAlignment(Qt.AlignLeft)
        grid_layout.addWidget(self.url_input, 0, 1)  # 第0行，第1列
        
        # 保存目录 - 标签右对齐
        dir_label = QLabel("保存目录:")
        dir_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        grid_layout.addWidget(dir_label, 1, 0)  # 第1行，第0列
        
        dir_input_layout = QHBoxLayout()
        self.dir_input = QLineEdit()
        self.dir_input.setPlaceholderText("请选择保存视频的目录")
        self.dir_input.setAlignment(Qt.AlignLeft)
        dir_input_layout.addWidget(self.dir_input)
        
        self.browse_btn = QPushButton("浏览...")
        self.browse_btn.clicked.connect(self.browse_directory)
        dir_input_layout.addWidget(self.browse_btn)
        
        grid_layout.addLayout(dir_input_layout, 1, 1)  # 第1行，第1列
        
        # 分辨率选择 - 标签右对齐
        resolution_label = QLabel("分辨率:")
        resolution_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        grid_layout.addWidget(resolution_label, 2, 0)  # 第2行，第0列
        
        self.resolution_combo = QComboBox()
        # 添加分辨率选项
        self.resolution_combo.addItem("自动选择最高画质", 0)
        self.resolution_combo.addItem("360P 流畅", 16)
        self.resolution_combo.addItem("480P 标清", 32)
        self.resolution_combo.addItem("720P 准高清 (普通用户)", 64)
        self.resolution_combo.addItem("1080P 高清 (普通用户)", 80)
        self.resolution_combo.addItem("1080P 高码率 (大会员)", 112)
        self.resolution_combo.addItem("1080P 60帧 (大会员)", 116)
        self.resolution_combo.addItem("4K 超高清 (大会员)", 120)
        
        # 连接信号，当分辨率变化时检查是否需要显示Cookie输入
        self.resolution_combo.currentIndexChanged.connect(self.check_resolution)
        grid_layout.addWidget(self.resolution_combo, 2, 1)  # 第2行，第1列
        
        # Cookie加载 - 标签右对齐
        cookie_label = QLabel("Cookie文件:")
        cookie_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        grid_layout.addWidget(cookie_label, 3, 0)  # 第3行，第0列
        
        # 创建水平布局容器
        cookie_container = QHBoxLayout()
        
        # 左侧：Cookie路径输入和加载按钮
        cookie_left = QHBoxLayout()
        self.cookie_path_input = QLineEdit()
        self.cookie_path_input.setPlaceholderText("选择包含Cookie的文件")
        self.cookie_path_input.setAlignment(Qt.AlignLeft)
        self.cookie_path_input.setReadOnly(True)
        cookie_left.addWidget(self.cookie_path_input, 4)  # 占4份宽度
        
        self.load_cookie_btn = QPushButton("加载...")
        self.load_cookie_btn.clicked.connect(self.load_cookie_from_file)
        cookie_left.addWidget(self.load_cookie_btn, 1)  # 占1份宽度
        
        # 右侧：帮助说明
        help_label = QLabel(
            "<b>如何获取Cookie:</b><br>"
            "1. 登录B站后按F12打开开发者工具<br>"
            "2. 切换到Network标签并刷新页面<br>"
            "3. 在任意请求的Headers中找到'cookie'字段<br>"
            "4. 复制整个cookie字符串到文本文件<br>"
            "<i>注意: 加载Cookie后可以下载更高分辨率视频</i>"
        )
        help_label.setWordWrap(True)
        help_label.setStyleSheet("""
            QLabel {
                background-color: #f0f0f0;
                border: 1px solid #cccccc;
                padding: 10px;
                font-size: 10pt;
                color: #555555;
            }
        """)
        
        # 添加左右部分到容器
        cookie_container.addLayout(cookie_left, 1)  # 左侧占1份
        cookie_container.addWidget(help_label, 2)   # 右侧占2份
        
        grid_layout.addLayout(cookie_container, 3, 1)  # 第3行，第1列
        
        self.layout.addLayout(grid_layout)
        
        # 按钮区域
        btn_layout = QHBoxLayout()
        
        # 断点续传选择框
        self.resume_checkbox = QCheckBox("断点续传")
        self.resume_checkbox.setChecked(True)
        self.resume_checkbox.setStyleSheet("""
            QCheckBox {
                color: #0055BB;
                font-weight: bold;
            }
        """)
        btn_layout.addWidget(self.resume_checkbox)
        
        # 添加弹性空间
        btn_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
        
        # 下载按钮
        self.download_btn = QPushButton("开始下载")
        self.download_btn.setStyleSheet("""
            QPushButton {
                background-color: #4CAF50;
                color: white;
                font-weight: bold;
                padding: 5px 15px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
            QPushButton:disabled {
                background-color: #cccccc;
            }
        """)
        self.download_btn.clicked.connect(self.start_download)
        btn_layout.addWidget(self.download_btn)
        
        # 取消按钮
        self.cancel_btn = QPushButton("取消下载")
        self.cancel_btn.setStyleSheet("""
            QPushButton {
                background-color: #f44336;
                color: white;
                font-weight: bold;
                padding: 5px 15px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #d32f2f;
            }
            QPushButton:disabled {
                background-color: #cccccc;
            }
        """)
        self.cancel_btn.clicked.connect(self.cancel_download)
        self.cancel_btn.setEnabled(False)
        btn_layout.addWidget(self.cancel_btn)
        
        self.layout.addLayout(btn_layout)
        
        # 进度条
        self.progress_label = QLabel("准备就绪")
        self.progress_label.setFont(QFont("Arial", 10))
        self.progress_label.setAlignment(Qt.AlignLeft)
        self.layout.addWidget(self.progress_label)
        
        self.progress_bar = QProgressBar()
        self.layout.addWidget(self.progress_bar)
        
        # 状态列表
        self.status_label = QLabel("任务状态:")
        self.status_label.setFont(QFont("Arial", 10, QFont.Bold))
        self.status_label.setAlignment(Qt.AlignLeft)
        self.layout.addWidget(self.status_label)
        
        self.status_list = QListWidget()
        self.status_list.setStyleSheet("""
            QListWidget {
                background-color: #f0f0f0;
                border: 1px solid #cccccc;
                font-family: Consolas, "Courier New", monospace;
            }
            QListWidget::item {
                padding: 5px;
                border-bottom: 1px solid #e0e0e0;
            }
            QListWidget::item:selected {
                background-color: #d0e0ff;
            }
        """)
        self.layout.addWidget(self.status_list, 1)
        
        # 状态栏 - 添加日志链接
        self.statusBar().setStyleSheet("""
            QStatusBar {
                background-color: #f0f0f0;
                border-top: 1px solid #cccccc;
            }
        """)
        
        # 日志链接
        self.log_link = QLabel("<a href='#' style='color: #0066cc; text-decoration: none;'>查看日志</a>")
        self.log_link.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.log_link.linkActivated.connect(self.open_log)
        self.log_link.setEnabled(False)
        self.statusBar().addPermanentWidget(self.log_link)
    
    def load_cookie_from_file(self):
        """从文件加载Cookie信息"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择Cookie文件", "", "文本文件 (*.txt);;所有文件 (*)"
        )
        
        if not file_path:
            return
            
        try:
            # 读取文件内容
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # 解析Cookie内容（跳过注释行）
            cookie_lines = []
            for line in content.splitlines():
                line = line.strip()
                if line and not line.startswith('#'):  # 跳过空行和注释行
                    cookie_lines.append(line)
            
            # 合并为单行Cookie字符串
            self.cookie = '; '.join(cookie_lines)
            self.cookie_path_input.setText(file_path)
            
            # 显示加载成功
            self.statusBar().showMessage(f"Cookie已加载: {os.path.basename(file_path)}", 5000)
            
            # 更新帮助说明
            self.statusBar().showMessage("Cookie已加载，现在可以下载高清视频", 3000)
            
        except Exception as e:
            QMessageBox.critical(self, "加载Cookie失败", f"无法读取Cookie文件: {str(e)}")
            self.cookie = ""
            self.cookie_path_input.clear()

    def check_resolution(self):
        """检查分辨率选择是否需要Cookie"""
        # 获取当前选择的分辨率ID
        resolution_id = self.resolution_combo.currentData()
        
        # 如果选择的是720P及以上分辨率(64,80,112,116,120)但没有Cookie
        if resolution_id in [64, 80, 112, 116, 120] and not self.cookie:
            QMessageBox.warning(
                self, 
                "需要Cookie", 
                "选择720P及以上分辨率需要加载Cookie文件\n\n"
                "没有Cookie时只能下载360P或480P视频"
            )
            # 自动切换到480P
            for i in range(self.resolution_combo.count()):
                if self.resolution_combo.itemData(i) == 32:
                    self.resolution_combo.setCurrentIndex(i)
                    break

    def browse_directory(self):
        """选择保存目录"""
        directory = QFileDialog.getExistingDirectory(self, "选择保存目录")
        if directory:
            self.dir_input.setText(directory)

    def start_download(self):
        """开始下载"""
        url = self.url_input.text().strip()
        save_dir = self.dir_input.text().strip()
        resume = self.resume_checkbox.isChecked()
        resolution_id = self.resolution_combo.currentData()
        
        if not url:
            QMessageBox.warning(self, "输入错误", "请输入Bilibili视频网址")
            return
            
        if not save_dir:
            QMessageBox.warning(self, "输入错误", "请选择保存目录")
            return
            
        # 检查720P及以上分辨率是否需要Cookie
        if resolution_id in [64, 80, 112, 116, 120] and not self.cookie:
            QMessageBox.warning(
                self, 
                "需要Cookie", 
                "选择720P及以上分辨率需要加载Cookie文件\n\n"
                "没有Cookie时只能下载360P或480P视频"
            )
            return
        
        if not os.path.exists(save_dir):
            try:
                os.makedirs(save_dir)
            except Exception as e:
                QMessageBox.critical(self, "错误", f"无法创建目录: {str(e)}")
                return
        
        # 清空状态列表
        self.status_list.clear()
        self.status_list.addItem("=== 开始下载任务 ===")
        self.status_list.addItem(f"网址: {url}")
        self.status_list.addItem(f"保存目录: {save_dir}")
        self.status_list.addItem(f"分辨率: {self.resolution_combo.currentText()}")
        self.status_list.addItem(f"断点续传: {'启用' if resume else '禁用'}")
        self.status_list.addItem(f"使用Cookie: {'是' if self.cookie else '否'}")
        self.status_list.addItem("=" * 50)
        
        # 禁用UI控件
        self.url_input.setEnabled(False)
        self.dir_input.setEnabled(False)
        self.browse_btn.setEnabled(False)
        self.resolution_combo.setEnabled(False)
        self.load_cookie_btn.setEnabled(False)
        self.resume_checkbox.setEnabled(False)
        self.download_btn.setEnabled(False)
        self.cancel_btn.setEnabled(True)
        
        # 创建并启动下载线程
        self.download_thread = DownloadThread(url, save_dir, resume, resolution_id, self.cookie)
        self.download_thread.update_status.connect(self.update_task_status)
        self.download_thread.update_progress.connect(self.update_progress)
        self.download_thread.finished.connect(self.download_finished)
        self.download_thread.error.connect(self.show_error)
        self.download_thread.resolution_warning.connect(self.show_resolution_warning)
        self.download_thread.cookie_status.connect(self.handle_cookie_status)  # 新增信号连接
        self.download_thread.start()
        
        # 保存日志文件路径
        self.log_file = self.download_thread.log_file
        self.log_link.setEnabled(True)
    
    def handle_cookie_status(self, is_valid, message):
        """处理Cookie验证结果"""
        if not is_valid:
            QMessageBox.warning(self, "Cookie验证失败", message)
            self.status_list.addItem(f"Cookie验证失败: {message}")
            self.cancel_download()
        else:
            self.status_list.addItem(f"Cookie验证成功: {message}")

    def show_resolution_warning(self, cid, title, requested, available):
        """显示分辨率警告弹窗"""
        # 获取分辨率名称映射
        resolution_names = {
            16: "360P 流畅",
            32: "480P 标清",
            64: "720P 准高清",
            80: "1080P 高清",
            112: "1080P 高码率",
            116: "1080P 60帧",
            120: "4K 超高清"
        }
        
        # 创建消息
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("分辨率警告")
        msg.setText(f"视频 '{title}' 无法获取请求的分辨率")
        
        # 添加详细信息
        details = (
            f"请求的分辨率: {resolution_names.get(requested, f'未知({requested})')}\n"
            f"实际使用的分辨率: {resolution_names.get(available, f'未知({available})')}\n\n"
            "是否继续下载？"
        )
        msg.setInformativeText(details)
        
        # 添加按钮
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.Yes)
        
        # 显示弹窗并获取结果
        result = msg.exec_()
        
        if result == QMessageBox.No:
            # 用户选择取消下载
            self.cancel_download()
            self.status_list.addItem(f"用户取消了下载 (CID: {cid})")
            
    def update_task_status(self, cid, status, message):
        """更新任务状态 - 只显示关键状态"""
        # 只处理重要状态，忽略中间状态
        important_statuses = ["start", "skip", "merging", "completed", "error", "cancel", "info"]
        
        if status not in important_statuses:
            return
            
        if cid:
            # 查找现有任务项
            items = self.status_list.findItems(f"CID: {cid}", Qt.MatchStartsWith)
            if items:
                item = items[0]
                text = item.text()
                # 更新状态部分
                if "状态:" in text:
                    parts = text.split("状态:")
                    if len(parts) > 1:
                        new_text = f"{parts[0]}状态: <b>{status}</b> | {message}"
                        item.setText(new_text)
                        
                # 根据状态设置颜色
                if status == "error":
                    item.setForeground(QColor("#d32f2f"))  # 红色
                elif status == "completed":
                    item.setForeground(QColor("#388e3c"))  # 绿色
                elif status == "merging":
                    item.setForeground(QColor("#7b1fa2"))  # 紫色
                    
                return
                
            # 创建新任务项
            item_text = f"CID: {cid} | 状态: <b>{status}</b> | {message}"
            item = QListWidgetItem(item_text)
            
            # 根据状态设置颜色
            if status == "error":
                item.setForeground(QColor("#d32f2f"))  # 红色
            elif status == "completed":
                item.setForeground(QColor("#388e3c"))  # 绿色
            elif status == "merging":
                item.setForeground(QColor("#7b1fa2"))  # 紫色
                
            self.status_list.addItem(item)
        else:
            # 添加普通消息
            self.status_list.addItem(message)
        
        # 滚动到底部
        self.status_list.scrollToBottom()

    def open_log(self):
        """打开日志文件"""
        if self.log_file and os.path.exists(self.log_file):
            try:
                webbrowser.open(self.log_file)
            except Exception as e:
                QMessageBox.warning(self, "打开日志失败", f"无法打开日志文件: {str(e)}")
        else:
            QMessageBox.information(self, "日志不可用", "日志文件尚未生成或已被删除")

    def cancel_download(self):
        """取消下载"""
        if self.download_thread and self.download_thread.isRunning():
            self.download_thread.cancel()
            self.cancel_btn.setEnabled(False)
            self.status_list.addItem("用户取消下载...")

    def update_progress(self, current, total):
        """更新进度条"""
        self.progress_bar.setMaximum(total)
        self.progress_bar.setValue(current)
        percentage = int(current / total * 100) if total > 0 else 0
        self.progress_label.setText(f"进度: {current}/{total} ({percentage}%)")

    def download_finished(self):
        """下载完成处理"""
        self.status_list.addItem("\n=== 下载任务完成 ===")
        self.statusBar().showMessage("下载完成")
        
        # 启用UI控件
        self.url_input.setEnabled(True)
        self.dir_input.setEnabled(True)
        self.browse_btn.setEnabled(True)
        self.resolution_combo.setEnabled(True)
        self.load_cookie_btn.setEnabled(True)
        self.resume_checkbox.setEnabled(True)
        self.download_btn.setEnabled(True)
        self.cancel_btn.setEnabled(False)
        
        self.download_thread = None

    def show_error(self, message):
        """显示错误信息"""
        QMessageBox.critical(self, "错误", message)
        self.status_list.addItem(f"错误: {message}")
        self.download_finished()

    def closeEvent(self, event):
        """关闭窗口时确保线程停止"""
        if self.download_thread and self.download_thread.isRunning():
            self.download_thread.cancel()
            self.download_thread.wait(2000)  # 等待2秒
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BilibiliDownloader()
    
    # 设置窗口图标（使用绝对路径或资源路径）
    icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "B_64x64.ico")
    if os.path.exists(icon_path):
        window.setWindowIcon(QIcon(icon_path))
    else:
        # 尝试使用资源路径
        try:
            window.setWindowIcon(QIcon("B_64x64.ico"))
        except:
            pass
            
    # 设置任务栏图标
    if sys.platform == "win32":
        import ctypes
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("Bilibili.Downloader.V1")
    
    window.show()
    sys.exit(app.exec_())